package com.coincalf.exchange.service.impl;

import com.baomidou.mybatisplus.mapper.EntityWrapper;
import com.baomidou.mybatisplus.service.impl.ServiceImpl;
import com.coincalf.exchange.entity.*;
import com.coincalf.exchange.mapper.AccountMapper;
import com.coincalf.exchange.service.AccountDetailService;
import com.coincalf.exchange.service.AccountService;
import com.coincalf.exchange.service.ForexAccountService;
import com.coincalf.exchange.service.UserService;
import com.coincalf.framework.constants.Constant;
import com.coincalf.framework.enums.AdminUserType;
import com.coincalf.framework.enums.AmountDirection;
import com.coincalf.framework.enums.BaseStatus;
import com.coincalf.framework.enums.BusinessType;
import com.coincalf.framework.exception.AccountException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;

import java.math.BigDecimal;
import java.util.List;

/**
 * <p>
 * 用户财产记录 服务实现类
 * </p>
 *
 * @author qiang
 * @since 2018-05-12
 */
@Slf4j
@Service
public class AccountServiceImpl extends ServiceImpl<AccountMapper, Account> implements AccountService, Constant {

    @Autowired
    private UserService userService;
    @Autowired
    private AccountDetailService accountDetailService;
    @Autowired
    private ForexAccountService forexAccountService;

    @Override
    public Account queryByUserIdAndCoinId(long userId, long coinId) {
        EntityWrapper<Account> wrapper = new EntityWrapper<>();
        wrapper.eq("user_id", userId)
                .eq("coin_id", coinId)
                .eq("status", BaseStatus.EFFECTIVE.getCode())
                .last("LIMIT 1");
        List<Account> accountList = baseMapper.selectList(wrapper);
        if (CollectionUtils.isEmpty(accountList)) {
            return null;
        }
        return accountList.get(0);
    }

    /**
     * 币币交易撮合完成后修改资金账户
     *
     * @param dealOrder 成交订单
     * @param buyOrder  委托买单
     * @param sellOrder 委托卖单
     * @param buyLock   买单需要重新冻结资金额度
     * @param sellLock  卖单需要重新冻结资金额度
     * @throws AccountException
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void tradeMatchModifyAccount(TurnoverOrder dealOrder,
                                        EntrustOrder buyOrder,
                                        EntrustOrder sellOrder,
                                        BigDecimal buyLock,
                                        BigDecimal sellLock) throws AccountException {
        // 管理员用户
        User adminUser = userService.queryAdminUser(AdminUserType.ADMIN);
        if (adminUser == null) {
            log.warn("管理员账户不存在，请设置管理员账户 username=Admin");
            throw new RuntimeException("管理员账户不存在");
        }
        // 解锁买单支付币种账户，并创建账户详情记录
        this.unlockAmount(buyOrder.getUserId(),         // 买单用户ID
                dealOrder.getBuyCoinId(),               // 买单币种
                buyOrder.getFreeze(),                   // 买单冻结资金
                BusinessType.TRADE_DEAL,                // 业务类型：币币交易撮合成交
                dealOrder.getId());                     // 成交订单ID
        // 解锁卖单账户
        this.unlockAmount(sellOrder.getUserId(),        // 卖单用户ID
                dealOrder.getSellCoinId(),              // 卖单币种
                sellOrder.getFreeze(),                  // 卖单冻结资金
                BusinessType.TRADE_DEAL,                // 业务类型：币币交易撮合成交
                dealOrder.getId());                     // 成交订单ID
        // 更新买单用户账户余额
        this.transferAmount(buyOrder.getUserId(),       // 买单用户ID
                sellOrder.getUserId(),                  // 卖单用户ID
                dealOrder.getBuyCoinId(),               // 买单支付币种
                dealOrder.getAmount(),                  // 买单成交金额
                BusinessType.TRADE_DEAL,                // 业务类型：币币交易撮合成交
                sellOrder.getId(),                      // 成交订单ID
                BusinessType.TRADE_DEAL.getDesc());     // 备注
        // 更新卖单用户账户余额
        this.transferAmount(sellOrder.getUserId(),      // 卖单用户ID
                buyOrder.getUserId(),                   // 买单用户ID
                dealOrder.getSellCoinId(),              // 卖单支付币种
                dealOrder.getVolume(),                  // 卖单成交数量
                BusinessType.TRADE_DEAL,                // 业务类型：币币交易撮合成交
                sellOrder.getId(),                      // 成交订单ID
                BusinessType.TRADE_DEAL.getDesc());     // 备注
        // 收取买单手续费
        this.transferAmount(buyOrder.getUserId(),       // 买单用户ID
                adminUser.getId(),                      // 管理员用户ID
                dealOrder.getBuyCoinId(),               // 买单支付币种
                dealOrder.getDealBuyFee(),              // 买单成交手续费
                BusinessType.TRADE_DEAL_FEE,            // 业务类型：币币交易撮合成交手续费
                sellOrder.getId(),                      // 成交订单ID
                BusinessType.TRADE_DEAL_FEE.getDesc()); // 备注
        // 收取卖单手续费
        this.transferAmount(sellOrder.getUserId(),      // 卖单用户ID
                adminUser.getId(),                      // 管理员用户ID
                dealOrder.getBuyCoinId(),               // 买单支付币种
                dealOrder.getDealSellFee(),             // 卖单成交手续费
                BusinessType.TRADE_DEAL_FEE,            // 业务类型：币币交易撮合成交手续费
                sellOrder.getId(),                      // 成交订单ID
                BusinessType.TRADE_DEAL_FEE.getDesc());    // 备注
        // 重新冻结买单资金
        if (buyLock != null && buyLock.compareTo(BigDecimal.ZERO) > 0) {
            // 冻结资金
            this.lockAmount(buyOrder.getUserId(),       // 买单用户ID
                    dealOrder.getBuyCoinId(),           // 买单支付币种ID
                    buyLock,                            // 买单冻结资金
                    BusinessType.TRADE_DEAL,            // 业务类型：币币交易撮合成交
                    dealOrder.getId());                 // 关联订单号
        }
        // 重新冻结卖单资金
        if (sellLock != null && sellLock.compareTo(BigDecimal.ZERO) > 0) {
            this.lockAmount(sellOrder.getUserId(),      // 卖单用户ID
                    dealOrder.getSellCoinId(),          // 卖单币种ID
                    sellLock,                           // 卖单冻结资金
                    BusinessType.TRADE_DEAL,            // 业务类型：币币交易撮合成交
                    dealOrder.getId());                 // 关联订单号
        }
    }

    /**
     * 创新交易撮合完成后修改资金账户
     *
     * @param market    交易对
     * @param dealOrder 成交订单
     * @param buyOrder  委托买单
     * @param sellOrder 委托卖单
     * @param buyLock   买单需要重新冻结资金额度
     * @param sellLock  卖单需要重新冻结资金额度
     * @throws AccountException
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void forexMatchModifyAccount(Market market,
                                        TurnoverOrder dealOrder,
                                        EntrustOrder buyOrder,
                                        EntrustOrder sellOrder,
                                        BigDecimal buyLock,
                                        BigDecimal sellLock) throws AccountException {
        // 管理员用户
        User adminUser = userService.queryAdminUser(AdminUserType.ADMIN);
        if (adminUser == null) {
            log.warn("管理员账户不存在，请设置管理员账户 username=Admin");
            throw new RuntimeException("管理员账户不存在");
        }
        // 解锁买单支付币种账户，并创建账户详情记录
        this.unlockAmount(buyOrder.getUserId(),         // 买单用户ID
                dealOrder.getBuyCoinId(),               // 买单币种
                buyOrder.getFreeze(),                   // 买单冻结资金
                BusinessType.FOREX_DEAL_FEE,            // 业务类型：币币交易撮合成交手续费
                dealOrder.getId());                     // 成交订单ID
        // 解锁卖单账户
        this.unlockAmount(sellOrder.getUserId(),        // 卖单用户ID
                dealOrder.getSellCoinId(),              // 卖单币种
                sellOrder.getFreeze(),                  // 卖单冻结资金
                BusinessType.FOREX_DEAL_FEE,            // 业务类型：币币交易撮合成交手续费
                dealOrder.getId());                     // 成交订单ID
        // 收取买单手续费
        this.transferAmount(buyOrder.getUserId(),       // 买单用户ID
                adminUser.getId(),                      // 管理员用户ID
                dealOrder.getBuyCoinId(),               // 买单支付币种
                dealOrder.getDealBuyFee(),              // 买单成交手续费
                BusinessType.FOREX_DEAL_FEE,            // 业务类型：币币交易撮合成交手续费
                sellOrder.getId(),                      // 成交订单ID
                BusinessType.FOREX_DEAL_FEE.getDesc()); // 备注
        // 收取卖单手续费
        this.transferAmount(sellOrder.getUserId(),      // 卖单用户ID
                adminUser.getId(),                      // 管理员用户ID
                dealOrder.getBuyCoinId(),               // 买单支付币种
                dealOrder.getDealSellFee(),             // 卖单成交手续费
                BusinessType.FOREX_DEAL_FEE,            // 业务类型：币币交易撮合成交手续费
                sellOrder.getId(),                      // 成交订单ID
                BusinessType.FOREX_DEAL_FEE.getDesc()); // 备注
        // 重新冻结买单资金
        if (buyLock != null && buyLock.compareTo(BigDecimal.ZERO) > 0) {
            // 冻结资金
            this.lockAmount(buyOrder.getUserId(),       // 买单用户ID
                    dealOrder.getBuyCoinId(),           // 买单支付币种ID
                    buyLock,                            // 买单冻结资金
                    BusinessType.FOREX_DEAL_FEE,        // 业务类型：币币交易撮合成交
                    dealOrder.getId());                 // 关联订单号
        }
        // 重新冻结卖单资金
        if (sellLock != null && sellLock.compareTo(BigDecimal.ZERO) > 0) {
            this.lockAmount(sellOrder.getUserId(),      // 卖单用户ID
                    dealOrder.getSellCoinId(),          // 卖单币种ID
                    sellLock,                           // 卖单冻结资金
                    BusinessType.FOREX_DEAL_FEE,        // 业务类型：币币交易撮合成交
                    dealOrder.getId());                 // 关联订单号
        }
        // 平仓买单
        forexAccountService.closePosition(market, dealOrder, buyOrder);
        // 平仓卖单
        forexAccountService.closePosition(market, dealOrder, sellOrder);
    }

    /**
     * 冻结资金
     *
     * @param userId       用户ID
     * @param coinId       币种ID
     * @param amount       冻结金额
     * @param businessType 业务类型
     * @param orderId      关联订单号
     * @return
     */
    @Override
    public boolean lockAmount(long userId,
                              long coinId,
                              BigDecimal amount,
                              BusinessType businessType,
                              long orderId) throws AccountException {
        Account account = this.queryByUserIdAndCoinId(userId, coinId);
        if (account == null) {
            log.error("冻结资金-资金账户异常，userId:{}, coinId:{}", userId, coinId);
            throw new AccountException("资金账户异常");
        }
        if (baseMapper.lockAmount(account.getId(), amount) > 0) {
            // 保存流水
            AccountDetail accountDetail = new AccountDetail(userId,
                    coinId,
                    account.getId(),
                    account.getId(),
                    orderId,
                    AmountDirection.OUT.getType(),
                    businessType.getCode(),
                    amount,
                    "冻结");
            accountDetailService.insert(accountDetail);
            return true;
        }
        log.error("冻结资金失败，orderId:{}, userId:{}, coinId:{}, amount:{}, businessType:{}",
                orderId, userId, coinId, amount, businessType.getCode());
        throw new AccountException("冻结资金失败");
    }

    /**
     * 解冻资金
     *
     * @param userId       用户ID
     * @param coinId       币种ID
     * @param amount       冻结金额
     * @param businessType 业务类型
     * @param orderId      关联订单号
     * @return
     */
    @Override
    public boolean unlockAmount(long userId,
                                long coinId,
                                BigDecimal amount,
                                BusinessType businessType,
                                long orderId) throws AccountException {
        Account account = this.queryByUserIdAndCoinId(userId, coinId);
        if (account == null) {
            log.error("解冻资金-资金账户异常，userId:{}, coinId:{}", userId, coinId);
            throw new AccountException("资金账户异常");
        }
        if (baseMapper.unlockAmount(account.getId(), amount) > 0) {
            // 保存流水
            AccountDetail accountDetail = new AccountDetail(userId,
                    coinId,
                    account.getId(),
                    account.getId(),
                    orderId,
                    AmountDirection.INCOME.getType(),
                    businessType.getCode(),
                    amount,
                    "解冻");
            accountDetailService.insert(accountDetail);
            return true;
        }
        log.error("解冻资金失败，orderId:{}, userId:{}, coinId:{}, amount:{}, businessType:{}",
                orderId, userId, coinId, amount, businessType.getCode());
        throw new AccountException("解冻资金失败");
    }

    /**
     * 增加资金(平仓盈亏)
     *
     * @param userId       用户ID
     * @param coinId       币种ID
     * @param amount       冻结金额
     * @param businessType 业务类型
     * @param remark       备注
     * @param orderId      关联订单号
     * @return
     */
    @Override
    public boolean addProfit(long userId,
                             long coinId,
                             BigDecimal amount,
                             BusinessType businessType,
                             String remark,
                             long orderId) throws AccountException {
        Account account = this.queryByUserIdAndCoinId(userId, coinId);
        if (account == null) {
            log.error("增加资金-资金账户异常，userId:{}, coinId:{}", userId, coinId);
            throw new AccountException("资金账户异常");
        }
        if (baseMapper.addAmount(account.getId(), amount, 1) > 0) {
            // 保存流水
            AccountDetail accountDetail = new AccountDetail(userId,
                    coinId,
                    account.getId(),
                    account.getId(),
                    orderId,
                    AmountDirection.INCOME.getType(),
                    businessType.getCode(),
                    amount,
                    remark);
            accountDetailService.insert(accountDetail);
            return true;
        }
        log.error("增加资金失败，orderId:{}, userId:{}, coinId:{}, amount:{}, businessType:{}",
                orderId, userId, coinId, amount, businessType.getCode());
        throw new AccountException("增加资金失败");
    }

    /**
     * 资金划转
     *
     * @param fromUserId   转出用户ID
     * @param toUserId     转入用户ID
     * @param coinId       币种ID
     * @param amount       金额
     * @param businessType 业务类型
     * @param orderId      关联订单号
     * @param remark       备注
     * @return
     */
    @Override
    public boolean transferAmount(long fromUserId,
                                  long toUserId,
                                  long coinId,
                                  BigDecimal amount,
                                  BusinessType businessType,
                                  long orderId,
                                  String remark) throws AccountException {
        Account fromAccount = this.queryByUserIdAndCoinId(fromUserId, coinId);
        if (fromAccount == null) {
            log.error("资金划转-资金账户异常，userId:{}, coinId:{}", fromUserId, coinId);
            throw new AccountException("资金账户异常");
        }
        Account toAccount = this.queryByUserIdAndCoinId(toUserId, coinId);
        if (toAccount == null) {
            log.error("资金划转-资金账户异常，userId:{}, coinId:{}", toUserId, coinId);
            throw new AccountException("资金账户异常");
        }
        // 扣减资金
        if (baseMapper.subtractAmount(fromAccount.getId(), amount) > 0) {
            // 保存流水
            AccountDetail fromAccountDetail = new AccountDetail(fromUserId,
                    coinId,
                    fromAccount.getId(),
                    toAccount.getId(),
                    orderId,
                    AmountDirection.OUT.getType(),
                    businessType.getCode(),
                    amount,
                    remark);
            accountDetailService.insert(fromAccountDetail);
            // 增加资金
            if (baseMapper.addAmount(toAccount.getId(), amount, 0) > 0) {
                // 保存流水
                AccountDetail toAccountDetail = new AccountDetail(toUserId,
                        coinId,
                        toAccount.getId(),
                        fromAccount.getId(),
                        orderId,
                        AmountDirection.INCOME.getType(),
                        businessType.getCode(),
                        amount,
                        remark);
                accountDetailService.insert(toAccountDetail);
                return true;
            }
        }
        log.error("资金划转，orderId:{}, fromUserId:{}, toUserId:{}, coinId:{}, amount:{}, businessType:{}, remark:{}",
                orderId, fromUserId, toUserId, coinId, amount, businessType.getCode(), remark);
        throw new AccountException("资金划转失败");
    }

}
